#include "config.h"

#define DBG_SUBSYS S_LIBINTERFACE

#include "lich_api.h"

#include "storage.h"
#include "volume.h"
#include "license.h"
#include "utils.h"
#include "dbg.h"

#define MULTITHREADING          1
#define COPY_THREAD_MAX         10
#define COPY_SIZE_MIN           32      /* 32M */

/*
 *   snapdiff data format:
 *   +------------+------------+------------+------------+-----------
 *   |  metadata  | chunk data | chunk data | chunk data |
 *   |            |     1M     |     1M     |     1M     |
 *   +------------+------------+------------+------------+-----------
 *
 *   metadata layout:
 *   +------------+------------+------------+------------+------------
 *   |   magic    |   offset   |    size    |  bit map   |
 *   |   32 Byte  |   off_t    |   size_t   | char * len |
 *   +------------+------------+------------+------------+------------
 */

typedef struct {
        int fd;
        fileid_t fileid;
        fileid_t snapid;
        void * (*worker)(void *_arg);
} arg_ext_t;

typedef struct {
        sem_t sem;
        int ret;
        arg_ext_t ext;
        off_t offset;
        size_t size;
} arg_t;

typedef enum {
        OP_NULL,
        OP_CREATE,
        OP_LIST,
        OP_CAT,
        OP_COPY,
        OP_ROLLBACK,
        OP_REMOVE,
        OP_CLONE,
        OP_FLAT,
        OP_PROTECT,
        OP_UNPROTECT,
        OP_DIFF,
} snap_op_t;

static void usage()
{
        fprintf(stderr, "\nusage:\n"
                "lich.snapshot --create <file name>@<snap name> --pool <pool>\n"
                "lich.snapshot --rollback <file name>@<snap name> --pool <pool>\n"
                "lich.snapshot --remove <file name>@<snap name> --pool <pool>\n"
                "lich.snapshot --protect <file name>@<snap name> [0|1] --pool <pool>\n"
                "lich.snapshot --list <file name> [-a] --pool <pool>\n"
                "lich.snapshot --clone <file name>@<snap name> <file name> --pool <pool> [-p 0|1]\n"
                "lich.snapshot --flat <file name> --pool <pool>\n"
                "lich.snapshot --copy <file name>@<snap name> [idx|start~count] <:file name> --pool <pool>\n"
                "lich.snapshot --diff <file name>@<snap src>~<snap dst> [idx|start~count] <:file name|--json> --pool <pool>\n"
                "lich.snapshot --cat <file name>@<snap name> --pool <pool>\n"
                "\n"
               );
}

int main(int argc, char *argv[])
{
        int ret, op = OP_NULL, force = 0, json = 0, tmp;
        char c_opt;
        int verbose = 0, all = 0, format = 0, priority = -1, protect = 0;
        const char *name = NULL, *to = NULL, *snap = NULL, *idx = NULL, *pool = NULL;

        dbg_info(0);

        (void) verbose;

        while (srv_running) {
                int option_index = 0;

                static struct option long_options[] = {
                        { "cat", required_argument, 0, 0},
                        { "protect", required_argument, 0, 0},
                        { "unprotect", required_argument, 0, 0},
                        { "copy", required_argument, 0, 0},
                        { "diff", required_argument, 0, 0},
                        { "flat", required_argument, 0, 0},
                        { "force", no_argument, 0, 0},
                        { "pool", required_argument, 0, 0},
                        { "json", no_argument, 0, 0},
                        { "create", required_argument, 0, 'c'},
                        { "list", required_argument, 0, 'l'},
                        { "destroy", required_argument, 0, 'd'},
                        { "rollback", required_argument, 0, 'r'},
                        { "remove", required_argument, 0, 'm'},
                        { "clone", required_argument, 0, 'o'},
                        { "send", required_argument, 0, 's'},
                        { "recv", required_argument, 0, 'e'},
                        { "all", 0, 0, 'a'},
                        { "format", 0, 0, 'f'},
                        { "verbose", 0, 0, 'v' },
                        { "help",    0, 0, 'h' },
                        { 0, 0, 0, 0 },
                };

                c_opt = getopt_long(argc, argv, "c:l:d:r:s:e:af:p:vh", long_options, &option_index);
                if (c_opt == -1)
                        break;

                switch (c_opt) {
                case 0:
                        switch (option_index) {
                        case 0:
                                op = OP_CAT;
                                name = optarg;
                                break;
                        case 1:
                                op = OP_PROTECT;
                                name = optarg;
                                break;
                        case 2:
                                op = OP_UNPROTECT;
                                name = optarg;
                                break;
                        case 3:
                                op = OP_COPY;
                                name = optarg;
                                if (argc == 6) {
                                        to = argv[3];
                                } else if (argc == 7) {
                                        idx = argv[3];
                                        to = argv[4];
                                } else {
                                        usage();
                                        EXIT(EINVAL);
                                }
                                break;
                        case 4:
                                op = OP_DIFF;
                                name = optarg;
                                if (argc == 6) {
                                        to = argv[3];
                                } else if (argc == 7) {
                                        idx = argv[3];
                                        to = argv[4];
                                } else {
                                        usage();
                                        EXIT(EINVAL);
                                }
                                break;
                        case 5:
                                op = OP_FLAT;
                                name = optarg;
                                break;
                        case 6:
                                force = 1;
                                break;
                        case 7:
                                pool = optarg;
                                break;
                        case 8:
                                json = 1;
                                break;
                        default:
                                fprintf(stderr, "Hoops, wrong op got!\n");
                                YASSERT(0); 
                        }

                        break;
                case 'c':
                        op = OP_CREATE;
                        name = optarg;
                        break;
                case 'l':
                        op = OP_LIST;
                        name = optarg;
                        break;
                case 'r':
                        op = OP_ROLLBACK;
                        name = optarg;
                        break;
                case 'o':
                        op = OP_CLONE;
                        name = optarg;
                        to = argv[3];
                        break;
                case 'm':
                        op = OP_REMOVE;
                        name = optarg;
                        break;
                case 'a':
                        all = 1;
                        break;
                case 'f':
                        if (!strcmp(optarg, "tree"))
                                format = 2;
                        else if (!strcmp(optarg, "json"))
                                format = 1;
                        else {
                                ret = EINVAL;
                                GOTO(err_ret, ret);
                        }
                        break;
                case 'p':
                        if (!is_zero_one_char(optarg)) {
                                ret = EINVAL;
                                GOTO(err_ret, ret);
                        }

                        tmp = atoi(optarg);
                        priority = tmp;
                        protect = tmp;

                        break;
                case 'v':
                        verbose = 1;
                        break;
                case 'h':
                        usage();
                        EXIT(0);
                default:
                        usage();
                        EXIT(EINVAL);
                }
        }

#if 1
        if (argc == 1) {
                usage();
                exit(EINVAL);
        }
#endif

        ret = env_init_simple("lich");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = network_connect_master();
        if (unlikely(ret)) {
            GOTO(err_ret, ret);
        }

        ret = stor_init(NULL, -1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = storage_license_check();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (op == OP_CREATE || op == OP_CAT || op == OP_COPY || op == OP_REMOVE ||
                        op == OP_ROLLBACK || op == OP_CLONE || op == OP_PROTECT) {
                snap = strchr(name, '@');
                if (!snap) {
                        fprintf(stderr, "snapshot name not specify\n");
                        ret = EINVAL;
                        GOTO(err_ret, ret);
                }

                if (!strncmp(snap + 1, LICH_SYSTEM_ATTR_PREFIX, strlen(LICH_SYSTEM_ATTR_PREFIX))) {
                        fprintf(stderr, "snapshot should not start with %s\n", LICH_SYSTEM_ATTR_PREFIX);
                        ret = EINVAL;
                        GOTO(err_ret, ret);
                }
        }

        switch (op) {
        case OP_CREATE:
                ret = utils_snapshot_create(pool ? pool : "default", name, force, protect);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_LIST:
                ret = utils_snapshot_list(pool ? pool : "default", name, all, format, verbose);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_CAT:
                ret = utils_snapshot_cat(pool ? pool : "default", name);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_COPY:
                ret = utils_snapshot_copy(pool ? pool : "default", name, to, idx);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_DIFF:
                ret = utils_snapshot_diff(pool ? pool : "default", name, to, idx, json);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_ROLLBACK:
                ret = utils_snapshot_rollback(pool ? pool : "default", name);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_REMOVE:
                ret = utils_snapshot_remove(pool ? pool : "default", name, force);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_CLONE:
                ret = utils_snapshot_clone(pool ? pool : "default", name, pool ? pool : "default", to, priority);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_FLAT:
                ret = utils_snapshot_flat(pool ? pool : "default", name);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_PROTECT:
                if (argc == 4) {
                        int num;

                        if (!is_zero_one_char(argv[3])) {
                                ret = EINVAL;
                                GOTO(err_ret, ret);
                        }

                        num = atoi(argv[3]);
                        ret = utils_snapshot_protect_set(pool ? pool : "default", name, num);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);
                } else if (argc == 3) {
                        ret = utils_snapshot_protect_get(pool ? pool : "default", name);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);
                } else {
                        ret = EINVAL;
                        GOTO(err_ret, ret);
                }

                break;
        case OP_UNPROTECT:
                usage();
                exit(EINVAL);

                break;
        default:
                usage();
                exit(EINVAL);
        }

        return 0;
err_ret:
        EXIT(_errno(ret));
}
